/********************************************************************************************/
/* Fullhan Technology (Shanghai) Co., Ltd.                                                  */
/* Fullhan Proprietary and Confidential                                                     */
/* Copyright (c) 2017 Fullhan Technology Group Limited and its affiliated companies         */
/********************************************************************************************/
#include "hw_cesa_if.h"
#include "tee_client_api.h"
#include <uapi/linux/if_alg.h>


/* optee TA UUID and cmds defines */
static TEEC_UUID TA_FH_AES_UUID = \
    { 0xdd00d7bd, 0xe9e3, 0x4dcb,\
    { 0xad, 0xbc, 0xda, 0x54, 0xa7, 0x1b, 0x9b, 0x65} };

#define TA_INVOKE_AES_PREPARE         1
#define TA_INVOKE_AES_SET_KEY         2
#define TA_INVOKE_AES_SET_IV          3
#define TA_INVOKE_AES_ENCRYPT         4
#define TA_INVOKE_AES_DECRYPT         5
#define TA_INVOKE_AES_START_TRANSFER  6

static TEEC_Context ctx;
static TEEC_Session sess;

static int tee_fh_aes_set_key(void *key, struct ex_key_map_para *key_map, size_t size)
{
	uint32_t paramTypes = TEEC_PARAM_TYPES(TEEC_MEMREF_TEMP_INPUT, TEEC_NONE,
					 TEEC_NONE, TEEC_NONE);
	TEEC_Parameter params[TEEC_CONFIG_PAYLOAD_REF_COUNT];

	memset(params, 0, sizeof(params));
	params[0].tmpref.buffer = key;
	params[0].tmpref.size = size;

	if (key_map) {
		paramTypes = TEEC_PARAM_TYPES(TEEC_MEMREF_TEMP_INPUT, TEEC_MEMREF_TEMP_INPUT,
						 TEEC_NONE, TEEC_NONE);
		params[1].tmpref.buffer = key_map;
		params[1].tmpref.size = sizeof(*key_map);
	}

	return tee_invoke_cmd(&sess, TA_INVOKE_AES_SET_KEY, paramTypes, params);
}

static int tee_fh_aes_set_iv(void *iv, size_t size)
{
	uint32_t paramTypes = TEEC_PARAM_TYPES(TEEC_MEMREF_TEMP_INPUT, TEEC_NONE,
					 TEEC_NONE, TEEC_NONE);
	TEEC_Parameter params[TEEC_CONFIG_PAYLOAD_REF_COUNT];

	memset(params, 0, sizeof(params));

	params[0].tmpref.buffer = iv;
	params[0].tmpref.size = size;

	return tee_invoke_cmd(&sess, TA_INVOKE_AES_SET_IV, paramTypes, params);
}

static int tee_fh_aes_prepare(int type, int mode, int flag)
{
	uint32_t paramTypes = TEEC_PARAM_TYPES(TEEC_VALUE_INPUT, TEEC_VALUE_INPUT,
					 TEEC_VALUE_INPUT, TEEC_NONE);
	TEEC_Parameter params[TEEC_CONFIG_PAYLOAD_REF_COUNT];

	memset(params, 0, sizeof(params));

	params[0].value.a = type;
	params[1].value.a = mode;
	params[2].value.a = flag;

	pr_debug("%x %x %x\n", type, mode, flag);

	return tee_invoke_cmd(&sess, TA_INVOKE_AES_PREPARE, paramTypes, params);
}

static int tee_fh_aes_start_transfer(void)
{
	uint32_t paramTypes = TEEC_PARAM_TYPES(TEEC_NONE, TEEC_NONE,
					 TEEC_NONE, TEEC_NONE);
	TEEC_Parameter params[TEEC_CONFIG_PAYLOAD_REF_COUNT];

	memset(params, 0, sizeof(params));


	pr_debug("aes start transfer\n");

	return tee_invoke_cmd(&sess, TA_INVOKE_AES_START_TRANSFER, paramTypes, params);
}

static int tee_fh_aes_crypto_action(void *src, void *dst, size_t size, int action)
{
	uint32_t paramTypes = TEEC_PARAM_TYPES(TEEC_MEMREF_TEMP_INPUT, TEEC_MEMREF_TEMP_OUTPUT,
					 TEEC_NONE, TEEC_NONE);
	TEEC_Parameter params[TEEC_CONFIG_PAYLOAD_REF_COUNT];

	memset(params, 0, sizeof(params));
	params[0].tmpref.buffer = src;
	params[0].tmpref.size = size;
	params[1].tmpref.buffer = dst;
	params[1].tmpref.size = size;

	return tee_invoke_cmd(&sess, action, paramTypes, params);
}

static int tee_fh_aes_encrypt(void *src, void *dst, size_t size)
{
	return tee_fh_aes_crypto_action(src, dst, size, TA_INVOKE_AES_ENCRYPT);
}

static int tee_fh_aes_decrypt(void *src, void *dst, size_t size)
{
	return tee_fh_aes_crypto_action(src, dst, size, TA_INVOKE_AES_DECRYPT);
}



enum {
	CRYPT_TYPE_AES,
	CRYPT_TYPE_DES,
	CRYPT_TYPE_DES3,
};

int32_t hw_cesa_ades_config(HW_CESA_ADES_CTRL_S *hw_ctrl)
{
	uint32_t key_size, iv_size;
	void *key_map = NULL;
	int err, crypto_type, crypto_mode;

	err = tee_open_session(&ctx, &sess, &TA_FH_AES_UUID);
	if (err) {
		pr_err("open_session failed: %d\n", err);
		return CESA_ADES_CONFIG_TEE_ERROR;
	}
	
	switch (hw_ctrl->algo_mode) {
	case HW_CESA_ADES_ALGO_MODE_DES:
		crypto_type = CRYPT_TYPE_DES;
		break;
	case HW_CESA_ADES_ALGO_MODE_TDES:
		crypto_type = CRYPT_TYPE_DES3;
		break;
	default:
		crypto_type = CRYPT_TYPE_AES;
		break;
	}

	crypto_mode = hw_ctrl->work_mode << 4;
	
	tee_fh_aes_prepare(crypto_type, crypto_mode, hw_ctrl->efuse_para.mode);
	tee_fh_aes_start_transfer();
	
	//================= KEY =================
	switch (hw_ctrl->algo_mode)
	{
	case HW_CESA_ADES_ALGO_MODE_DES:
		key_size = 8;
		break;
	case HW_CESA_ADES_ALGO_MODE_TDES:
		key_size = 24;
		break;
	case HW_CESA_ADES_ALGO_MODE_AES128:
		key_size = 16;
		break;
	case HW_CESA_ADES_ALGO_MODE_AES192:
		key_size = 24;
		break;
	case HW_CESA_ADES_ALGO_MODE_AES256:
		key_size = 32;
		break;
	default:
		printk("cipher error algo_mode!!\n");
		return CESA_ADES_ALGO_MODE_ERROR;
	}
#if 0
	if (ades_use_efuse_key(key_size,
		hw_ctrl->efuse_para.mode,
		hw_ctrl->efuse_para.ex_key_para.map_size,
		(uint32_t (*)[2])hw_ctrl->efuse_para.ex_key_para.map,
		(hw_ctrl->oper_mode == HW_CESA_ADES_OPER_MODE_ENC))
		== false) {
		memcpy((uint8_t *) &temp_key_buf[0], hw_ctrl->key, key_size);
		for (i = 0; i < key_size / sizeof(uint32_t); i++)
	    	biglittle_swap((uint8_t *) (temp_key_buf + i));
		memcpy((u8 *)ClearKey0, temp_key_buf, key_size);
	}
#endif
	if (hw_ctrl->efuse_para.mode & CRYPTO_EX_MEM_SWITCH_KEY)
		key_map = &hw_ctrl->efuse_para.ex_key_para;
	else 
		key_map = NULL;
		
	tee_fh_aes_set_key(hw_ctrl->key, key_map, key_size);

	//================= IV =================
	switch (hw_ctrl->work_mode)
	{
	case HW_CESA_ADES_WORK_MODE_CBC:
	case HW_CESA_ADES_WORK_MODE_CTR:
	case HW_CESA_ADES_WORK_MODE_CFB:
	case HW_CESA_ADES_WORK_MODE_OFB:
		if (hw_ctrl->algo_mode >= HW_CESA_ADES_ALGO_MODE_AES128)
			iv_size = 16;
		else
			iv_size = 8;
		break;
	case HW_CESA_ADES_WORK_MODE_ECB:
		iv_size = 0;
		break;
	default:
		printk("cipher error work_mode!!\n");
		return CESA_ADES_WORK_MODE_ERROR;
	}

	if (iv_size)
		tee_fh_aes_set_iv(hw_ctrl->iv_init, iv_size);

	//================= algo_mode & work_mode conflict check =================
	if (hw_ctrl->algo_mode <= HW_CESA_ADES_ALGO_MODE_TDES
		&& hw_ctrl->work_mode == HW_CESA_ADES_WORK_MODE_CTR)
		return CESA_ADES_WORK_ALGO_MODE_CONFLICT;
	
	return CESA_SUCCESS;
}

int32_t hw_cesa_ades_process(HW_CESA_ADES_CTRL_S *hw_ctrl, uint32_t src_addr, uint32_t dst_addr, uint32_t length)
{
	int err;

	if ((src_addr & 0x00000003) || (dst_addr & 0x00000003)) {
		printk("ades at dma mode: input or output address is not 4bytes multiple\n");
		return CESA_ADES_DMA_ADDR_ERROR;
	}

	//length check
	/* AES/DES CFB data length 1 byte align is permitted */
	if (hw_ctrl->algo_mode >= HW_CESA_ADES_ALGO_MODE_AES128) {
		if (hw_ctrl->work_mode != HW_CESA_ADES_WORK_MODE_CFB) {
			if (length & 0xF) {
				printk("error: AES ECB/CBC/OFB/CTR data length should be 16 byte align!\n");
				return CESA_ADES_DATA_LENGTH_ERROR;
			}
		}
	} else {
		if (hw_ctrl->work_mode != HW_CESA_ADES_WORK_MODE_CFB) {
			if (length & 0x7) {
				printk("error: DES ECB/CBC/OFB data length should be 8 byte align!\n");
				return CESA_ADES_DATA_LENGTH_ERROR;
			}
		}
	}


	if (hw_ctrl->oper_mode == HW_CESA_ADES_OPER_MODE_ENC)
		err = tee_fh_aes_encrypt((void *)src_addr, (void *)dst_addr, length);
	else
		err = tee_fh_aes_decrypt((void *)src_addr, (void *)dst_addr, length);

	tee_close_session(&ctx, &sess);

	return err ? CESA_ADES_PROCESS_TEE_ERROR : CESA_SUCCESS;
}

